<template>
  <el-upload
      action="#"
      :auto-upload="false"
      :on-change="handleFileChange"
      :show-file-list="false"
      drag
  >
    <el-icon class="el-icon--upload">
      <upload-filled/>
    </el-icon>
    <div class="el-upload__text">将大文件拖到此处，或<em>点击上传</em></div>
    <template #tip>
      <div class="el-upload__tip">支持大文件分片上传和断点续传，单文件大小不超过4GB</div>
    </template>
  </el-upload>

  <!-- 上传进度条 -->
  <div v-if="uploadProgress > 0 && uploadProgress < 100">
    <el-progress :percentage="uploadProgress" :stroke-width="15" :text-inside="true"/>
    <p>上传中... {{ uploadProgress }}%</p>
  </div>
  <div v-if="uploadProgress === 100">
    <el-alert title="文件上传成功！" type="success" :closable="false"/>
  </div>
</template>
<script setup>

import {ref, defineProps} from 'vue';
import {ElMessage} from 'element-plus';
import SparkMD5 from 'spark-md5'; // 需要安装此库：npm install spark-md5

// 响应式数据
const uploadProgress = ref(0); // 上传总进度
const currentFile = ref(null); // 当前文件
const fileHash = ref(''); // 文件哈希值

const props = defineProps({
  chunkSize: {
    type: Number,
    default: 2 * 1024 * 1024, // 分片大小， 默认2MB
  },
  maxRetry: {
    type: Number,
    default: 0, // 最大重试次数, 默认不重试
  },
  checkUrl: {
    type: String,
    default: '',
  },
  chunkUrl: {
    type: String,
    default: '',
  },
  mergeUrl: {
    type: String,
    default: '',
  },
})

// 1. 文件选择变更
const handleFileChange = (file) => {
  currentFile.value = file.raw;
  // 开始处理上传流程
  startUpload();
};

// 2. 开始上传流程
const startUpload = async () => {
  if (!currentFile.value) return;

  try {
    // a. 计算文件哈希（作为唯一标识）
    fileHash.value = await calculateFileHash(currentFile.value);

    const checkObj = await checkFileStatus(fileHash.value, currentFile.value.name);

    // c. 创建文件分片
    const chunkSize = props.chunkSize; // 每片2MB
    const chunks = createFileChunks(currentFile.value, chunkSize);
    // d. 上传缺失的分片
    await uploadChunks(chunks, checkObj?.data?.chunks, fileHash.value);

    // e. 通知服务端合并文件
    await mergeFileRequest(fileHash.value, currentFile.value.name, chunks.length);

    ElMessage.success('文件上传并合并完成！');
    uploadProgress.value = 100;

  } catch (error) {
    console.error('上传失败:', error);
    ElMessage.error('文件上传失败，请重试');
  }
};

// 3. 计算文件MD5（使用SparkMD5库）
const calculateFileHash = (file) => {
  return new Promise((resolve) => {
    const spark = new SparkMD5.ArrayBuffer();
    const fileReader = new FileReader();
    const chunkSize = 2 * 1024 * 1024;
    const chunks = Math.ceil(file.size / chunkSize);
    let currentChunk = 0;

    fileReader.onload = (e) => {
      spark.append(e.target.result);
      currentChunk++;
      if (currentChunk < chunks) {
        loadNextChunk();
      } else {
        resolve(spark.end()); // 返回最终的哈希值
      }
    };

    const loadNextChunk = () => {
      const start = currentChunk * chunkSize;
      const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
      const chunk = file.slice(start, end);
      fileReader.readAsArrayBuffer(chunk);
    };

    loadNextChunk();
  });
};

// 4. 创建文件分片
const createFileChunks = (file, chunkSize) => {
  const chunks = [];
  for (let i = 0; i < file.size; i += chunkSize) {
    const chunk = file.slice(i, i + chunkSize);
    chunks.push({index: i / chunkSize, chunk, hash: fileHash.value});
  }
  return chunks;
};

// 5. 上传分片
const uploadChunks = async (chunks, uploadedChunks, fileHash) => {
  // 过滤已上传的分片
  const chunksToUpload = chunks.filter(chunk => !uploadedChunks.includes(chunk.index));

  // 分批处理
  const concurrentLimit = 1;
  for (let i = 0; i < chunksToUpload.length; i += concurrentLimit) {
    const batch = chunksToUpload.slice(i, i + concurrentLimit);

    // 创建当前批次的上传请求
    const batchRequests = batch.map(chunk => {
      const formData = new FormData();
      formData.append('totalSize', chunks.length);
      formData.append("file", chunk.chunk);
      formData.append('chunkMd5', chunk.hash);
      formData.append('md5', fileHash);
      formData.append('chunkIndex', chunk.index);
      formData.append('fileName', currentFile.value.name);

      return uploadChunkRequest(formData);
    });

    // 等待当前批次完成
    await Promise.all(batchRequests);

    // 更新进度
    const completedChunks = i + batch.length;
    uploadProgress.value = Math.round((completedChunks / chunks.length) * 100);
  }
};

// ---------- 与服务端交互的API函数（需根据你的后端接口调整） ----------

// 检查文件状态
const checkFileStatus = async (md5, filename) => {
  // const response = await fetch('http://localhost:9991/upload/breakpoint/check', {
  const response = await fetch(props.checkUrl, {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({md5, filename})
  });
  return await response.json(); // 返回示例: { exists: false, uploadedChunks: [0,1,2] }
};

const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// 上传单个分片
const uploadChunkRequest = async (formData, retries = props.maxRetry) => {
  try {
    const response = await fetch(props.chunkUrl, {
      method: 'POST',
      body: formData,
    });
    return response;
  } catch (error) {
    if (retries > 0) {
      await wait(1000);
      return uploadChunkRequest(formData, retries - 1);
    }
    throw error;
  }
};

// 请求合并文件
const mergeFileRequest = async (hash, filename, totalChunks) => {
  await fetch(props.mergeUrl, {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      filename,
      md5: hash,
    })
  });
};

</script>

<script lang="scss">

</script>